package org.friut.web.persistence.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

/**
 * Abstract DAO. </br>
 * Extends HibernateDaoSupport base operation
 * 
 * @author Tang 
 * @since 2012/9/22
 */
@SuppressWarnings("unchecked")
public abstract class AuthDao<T> extends HibernateDaoSupport {
	
	private static Logger LOG = LoggerFactory.getLogger(AuthDao.class);
	
	private Class<T> entityClass;

	/**
	 * Important construct function. </br>
	 * Do generic conversion
	 */ 
	public AuthDao() {
		entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}

	/**
	 * Save or update
	 * 
	 * @param domain
	 */
	public boolean saveOrUpdate(T domain) {
		try {
			getHibernateTemplate().saveOrUpdate(domain);
		} catch (Exception e) {
			LOG.error("Error save or update.{}", e.toString());
			return false;
		}
		return true;
	}

	/**
	 * Delete
	 * 
	 * @param domain
	 */
	public boolean delete(T domain) {
		try {
			getHibernateTemplate().delete(domain);
		} catch (Exception e) {
			LOG.error("Error delete.{}", e.toString());
			return false;
		}
		return true;
	}

	/**
	 * Select domain
	 * 
	 * @param id
	 */
	public T select(Serializable id) {
		return id != null ? (T) getHibernateTemplate().get(entityClass, id) : null;
	}

	/**
	 * Select all domains. </br>
	 * Warning! It's maybe out of memory! 
	 * 
	 * @deprecated
	 */
	public List<T> selectAll(){
		DetachedCriteria criteria = DetachedCriteria.forClass(entityClass);
		return getHibernateTemplate().findByCriteria(criteria);
	}
	
	/**
	 * Select total count. </br>
	 * Count by certain properties.
	 * 
	 * @param propertyNames
	 * @param values
	 * @return
	 */
	public int totalCount(String[] propertyNames, Object[] values) {
		int totalCount = 0;
		try {
			StringBuffer sb = new StringBuffer();
			sb.append("SELECT COUNT(*) FROM " + entityClass.getName() + " AS model WHERE 1=1");
			for (int i = 0; i < propertyNames.length; i++) {
				sb.append(" AND ").append("model." + propertyNames[i] + "= ?");
			}
			Object result = getHibernateTemplate().find(sb.toString(), values).get(0);
			totalCount = Integer.valueOf(String.valueOf(result));
		} catch (RuntimeException e) {
			LOG.error("Error total count. {}", e.toString());
		}
		return totalCount;
	}
	
	/**
	 * Select total count. </br>
	 * Count by uncertainty properties.
	 * 
	 * @param propertyNames
	 * @param keyword
	 * @return
	 */
	public int totalCount(String[] propertyNames, Object keyword) {
		int totalCount = 0;
		try {
			StringBuffer sb = new StringBuffer();
			sb.append("SELECT COUNT(*) FROM " + entityClass.getName() + " AS model ");
			if (propertyNames.length >= 1 && keyword != null ) {
				sb.append("WHERE ");
				for (int i = 0; i < propertyNames.length - 1; i++) {
					sb.append("model." + propertyNames[i] + " like ?").append(" OR ");
				}
				sb.append("model." + propertyNames[propertyNames.length - 1] + " like ?");
				
				Object[] values = new Object[propertyNames.length];
				Arrays.fill(values, "%" + keyword + "%");
				Object result = getHibernateTemplate().find(sb.toString(), values).get(0);
				totalCount = Integer.valueOf(String.valueOf(result));
			} else {
				Query query = getSession().createQuery(sb.toString());
				totalCount = Integer.valueOf(String.valueOf(query.uniqueResult()));
			}
		} catch (RuntimeException e) {
			LOG.error("Error total count. {}", e.toString());
		}
		return totalCount;
	}
	
	/**
	 * Select list by property.
	 * 
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public List<T> selectListByProperty(String propertyName, Object value) {
		List<T> list = new ArrayList<T>();
		try {
			String queryString = "From " + entityClass.getName() + " AS model WHERE model." + propertyName + "= ?";
			list = getHibernateTemplate().find(queryString, value);
		} catch (RuntimeException e) {
			LOG.error("Error select list by property. {}", e.toString());
		}
		return list;
	}
	
	/**
	 * Select list by property map.
	 * 
	 * @param propertyMap
	 * @return
	 */
	public List<T> selectListByPropertys(Map<String, Object> propertyMap) {
		List<T> list = new ArrayList<T>();
		try {
			StringBuffer sb = new StringBuffer();
			sb.append("From " + entityClass.getName() + " AS model WHERE 1=1 ");
			ArrayList<Object> values = new ArrayList<Object>();
			for (Entry<String, Object> entry : propertyMap.entrySet()) {
				sb.append("model." + entry.getKey() + "= ?").append(" AND ");
				values.add(entry.getValue());
			}
			list = getHibernateTemplate().find(sb.toString(), values.toArray());
		} catch (RuntimeException e) {
			LOG.error("Error get list by property. {}", e.toString());
		}
		return list;
	}
	
	/**
	 * Select list by property
	 * 
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public List<T> selectListByPropertys(String[] propertyNames, Object[] values) {
		List<T> list = new ArrayList<T>();
		if (propertyNames.length == 0 || propertyNames.length != values.length) {
			return list;
		}
		
		try {
			StringBuffer sb = new StringBuffer();
			sb.append("From " + entityClass.getName() + " AS model WHERE 1=1 ");
			for (int i = 0; i < propertyNames.length; i++) {
				sb.append(" AND ").append("model." + propertyNames[i] + "= ?");
			}
			list = getHibernateTemplate().find(sb.toString(), values);
		} catch (RuntimeException e) {
			LOG.error("Error select list by property. {}", e.toString());
		}
		return list;
	}
	
	/**
	 * Select list by property. </br>
	 * Support pagination by start and limit
	 * 
	 * @param propertyName
	 * @param value
	 * @param start
	 * @param limit
	 * @return
	 */
	public List<T> selectListByPropertys(String[] propertyNames, Object[] values, int start, int limit) {
		List<T> list = new ArrayList<T>();
		try {
			StringBuffer sb = new StringBuffer();
			sb.append("From " + entityClass.getName() + " AS model WHERE 1=1 ");
			for (int i = 0; i < propertyNames.length; i++) {
				sb.append(" AND ").append("model." + propertyNames[i] + "= ?");
			}
			Query query = getSession().createQuery(sb.toString());
			query.setFirstResult(start);
			query.setMaxResults(limit);
			for (int i = 0; i < values.length; i++) {
				query.setParameter(i, values[i]);
			}
			
			list = query.list();
		} catch (RuntimeException e) {
			LOG.error("Error select list by property. {}", e.toString());
		}
		return list;
	}
	
	/**
	 * Support pagination by start and limit
	 * 
	 * @param start
	 * @param limit
	 * @return
	 */
	public List<T> selectListByPropertys(int start, int limit) {
		return selectListByPropertys(new String[]{}, new String[]{}, start, limit);
	}
	
	/**
	 * Select list by property. The value is keyword. </br>
	 * Support pagination by start and limit. </br>
	 * Support like search.
	 * 
	 * @param propertyName
	 * @param value
	 * @param start
	 * @param limit
	 * @return
	 */
	public List<T> selectListByKeyword(String[] propertyNames, Object keyword, int start, int limit) {
		if (keyword == null || keyword.equals("") || propertyNames.length == 0) {
			return selectListByPropertys(start, limit);
		}
		
		Object[] values = new Object[propertyNames.length];
		Arrays.fill(values, "%" + keyword + "%");
		List<T> list = new ArrayList<T>();
		try {
			StringBuffer sb = new StringBuffer();
			sb.append("From " + entityClass.getName() + " AS model ");
			if (propertyNames.length >= 1 && keyword != null ) {
				sb.append("WHERE ");
				for (int i = 0; i < propertyNames.length - 1; i++) {
					sb.append("model." + propertyNames[i] + " like ?").append(" OR ");
				}
				sb.append("model." + propertyNames[propertyNames.length - 1] + " like ?");
			}
			
			Query query = getSession().createQuery(sb.toString());
			query.setFirstResult(start);
			query.setMaxResults(limit);
			for (int i = 0; i < values.length; i++) {
				query.setParameter(i, values[i]);
			}
			
			list = query.list();
		} catch (RuntimeException e) {
			LOG.error("Error select list by keyword. {}", e.toString());
		}
		return list;
	}
	
	/**
	 * Save list batch
	 */
	public void saveList(List<T> list){
		SessionFactory sessionFactory = getHibernateTemplate().getSessionFactory();
		Session session = sessionFactory.openSession();
		session.beginTransaction();
		
		for (T log : list) {
			session.save(log);
		}
		session.getTransaction().commit();
        session.close();
	}
	
	// -------------------------Native SQL------------------------------------

	/**
	 * Select list by native SQL 
	 * 
	 * @param nativeSql
	 * @return
	 */
	public List<T> selectListByNativeSQL(String nativeSql) {
		return getSession().createSQLQuery(nativeSql).list();
	}

	/**
	 * Update by native SQL
	 * 
	 * @param nativeSql
	 * @return
	 */
	public int updateByNativeSQL(String nativeSql) {
		return getSession().createSQLQuery(nativeSql).executeUpdate();
	}

	// -------------------------HQL------------------------------------

	/**
	 * Select list by HSQL
	 * 
	 * @param hsql
	 * @return
	 */
	public List<T> selectListByHSQL(String hsql) {
		return (List<T>) getHibernateTemplate().find(hsql);
	}

	// ---------------------------DetachedCriteria-------------------

	/**
	 * It's depend of DetachedCriteria. </br>
	 * 
	 * @param criteria
	 */
	public List<T> getListByCriteria(DetachedCriteria criteria) {
		return (List<T>) getHibernateTemplate().findByCriteria(criteria);
	}

	/**
	 * It's depend of DetachedCriteria. </br>
	 * 
	 * @param criteria
	 * @param start 
	 * @param limit
	 */
	public List<T> getListByCriteria(DetachedCriteria criteria, int start, int limit) {
		return (List<T>) getHibernateTemplate().findByCriteria(criteria, start, limit);
	}
}
